﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

using mvcForum.Core;
using mvcForum.Core.Abstractions.Interfaces;
using mvcForum.Core.Events;
using mvcForum.Core.Interfaces.Data;
using mvcForum.Core.Interfaces.Search;
using mvcForum.Core.Interfaces.Services;
using mvcForum.Core.Specifications;
using mvcForum.Web.Attributes;
using mvcForum.Web.Controllers;
using mvcForum.Web.Extensions;
using mvcForum.Web.Helpers;
using mvcForum.Web.ViewModels;
using mvcForum.Web.ViewModels.Create;
using mvcForum.Web.ViewModels.Update;
using mvcForum.Web.Interfaces;
using mvcForum.Core.Interfaces.Events;

namespace mvcForum.Web.Areas.Forum.Controllers {

	/// <summary>
	/// Topic Controller.
	/// The controller handles creating and reading topics.
	/// </summary>
	public class TopicController : ThemedForumBaseController {
		private readonly IConfiguration config;
		private readonly IEventPublisher eventPublisher;
		private readonly IPostService postService;
		private readonly IRepository<TopicTrack> ttRepo;

		private readonly IAttachmentService attachmentService;
		private readonly ITopicService topicService;
		private readonly IPostRepository postRepo;

		#region Contructors
		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="config">Injected IConfiguration instance.</param>
		public TopicController(IWebUserProvider userProvider,
								IContext context,
								IConfiguration config,
								IEventPublisher eventPublisher,
								ITopicService topicService,
								IAttachmentService attachmentService,
								IPostService postService,
								IPostRepository postRepo)

			: base(userProvider, context) {

			// Store the configuration instance as a variable on the controller instance.
			this.config = config;
			this.eventPublisher = eventPublisher;
			this.ttRepo = this.context.GetRepository<TopicTrack>();
			this.postRepo = postRepo;
			this.attachmentService = attachmentService;
			this.topicService = topicService;
			this.postService = postService;
		}
		#endregion

		#region Create
		[Authorize]
		public ActionResult Create(Int32 id) {
			// Get the forum
			Core.Forum forum = this.GetRepository<Core.Forum>().Read(id);

			// Does the user have post access to the forum?
			if (forum.HasAccess(AccessFlag.Post)) {
				// Create the model
				CreateTopicViewModel model = new CreateTopicViewModel(forum, this.config.TopicsPerPage, this.config.AuthorNameSetting);
				model.Path = new Dictionary<String, String>();
				// Build the breadcrumb path.
				HomeController.BuildPath(forum, model.Path, this.Url);
				// TODO: Text!
				model.Path.Add("/", "New topic");
				return View(model);
			}

			// The user is not allowed to post in the forum, let's tell him!
			TempData.Add("Reason", ForumHelper.GetString<mvcForum.Web.ForumConfigurator>("NoAccess.ForumPosting", new { Name = forum.Name }));
			// Off to the "no access" page.
			return RedirectToRoute("NoAccess");
		}

		[Authorize]
		[HttpPost]
		[NotBanned]
		// TODO: Remove this and get it working with AllowHtml instead!!
		[ValidateInput(false)]
		public ActionResult Create(CreateTopicViewModel newTopic, HttpPostedFileBase[] files) {
			Core.Forum forum = this.GetRepository<Core.Forum>().Read(newTopic.ForumId);
			if (ModelState.IsValid) {
				List<String> feedback = new List<String>();
				Topic topic = this.topicService.Create(
														this.ActiveUser,
														forum,
														newTopic.Subject,
														newTopic.Type,
														newTopic.Body,
														Request.UserHostAddress,
														Request.UserAgent,
														Url.RouteUrl("ShowForum", new { id = forum.Id, title = forum.Name.ToSlug(), area = "forum" }),
														feedback
													);
				if (topic != null) {
					AccessFlag flag = forum.GetAccess();
					// Can attach file(s)?
					if ((flag & AccessFlag.Upload) == AccessFlag.Upload) {
						// Any file(s) uploaded directly on the create topic page?
						if (files != null && files.Length > 0 /*&& this.config.InlineUpload*/) {

							foreach (HttpPostedFileBase file in files) {
								if (file != null) {
									AttachStatusCode returnValue = this.attachmentService.AttachFile(this.ActiveUser, topic, file.FileName, file.ContentType, file.ContentLength, file.InputStream);
									if (returnValue != AttachStatusCode.Success) {
										feedback.Add(ForumHelper.GetString(returnValue.ToString(), new { File = file.FileName, Size = file.ContentLength, MaxFileSize = this.config.MaxFileSize, MaxAttachmentsSize = this.config.MaxAttachmentsSize, Extensions = this.config.AllowedExtensions }, "mvcForum.Web.AttachmentErrors"));
									}
								}
							}
						}
						else if (newTopic.AttachFile) {
							return RedirectToAction("Attach", "File", new RouteValueDictionary { { "id", topic.Posts.OrderBy(p => p.Posted).First().Id } });
						}
					}

					if (feedback.Any()) {
						TempData.Add("Feedback", feedback.Select(f => new MvcHtmlString(f)));
					}

					return RedirectToRoute("ShowForum", new RouteValueDictionary { { "id", forum.Id }, { "title", forum.Name.ToSlug() } });
				}
			}

			newTopic = new CreateTopicViewModel(forum, this.config.TopicsPerPage, this.config.AuthorNameSetting);
			newTopic.Path = new Dictionary<String, String>();
			HomeController.BuildPath(forum, newTopic.Path, this.Url);
			// TODO: Text!
			newTopic.Path.Add("/", "New topic");

			return View(newTopic);
		}
		#endregion

		#region Read
		public ActionResult Index(Int32 id, String title, Int32? page, String additional) {
			// Get the topic.
			Topic topic = this.topicService.Read(this.ActiveUser, id);
			if (topic != null) {
				// Is the title correct?
				if (title != topic.Title.ToSlug()) {
					// No, let's permanent redirect to the correct URL!
					return RedirectPermanent(
												Url.RouteUrl("ShowTopic", new {
													area = "forum",
													title = topic.Title.ToSlug(),
													id = topic.Id
												})
											);
				}

				// Let's get the posts!
				IEnumerable<Post> posts;
				Post lastReadPost = null;
				if (!String.IsNullOrWhiteSpace(additional) && this.Authenticated && this.ActiveUser != null) {
					DateTime? lastRead;
					Int32 showingPage;
					posts = this.postRepo.ReadSinceLast(this.ActiveUser, topic, this.config.MessagesPerPage, this.config.ShowDeletedMessages, out lastRead, out showingPage);
					if (lastRead.HasValue) {
						lastReadPost = posts.Where(p => p.Posted > lastRead.Value).OrderBy(p => p.Posted).FirstOrDefault();
					}
					page = showingPage;
				}
				else {
					posts = this.postRepo.Read(this.ActiveUser, topic, page.HasValue ? (page.Value > 0 ? page.Value : 1) : 1, this.config.MessagesPerPage, this.config.ShowDeletedMessages);
				}

				// The topic has been shown!
				topic.TrackAndView();

				//Let's create the model.
				TopicViewModel model = new TopicViewModel(
															topic,
															posts.Select(p => new MessageViewModel(p, this.config.AuthorNameSetting) { LastRead = (lastReadPost == null ? false : (p.Id == lastReadPost.Id) ? true : false), Attachments = p.Attachments.Select(a => new AttachmentViewModel(a)) }),
															this.postRepo.ReadAll(this.ActiveUser, topic, this.config.ShowDeletedMessages).Count() - 1,
															this.config.MessagesPerPage,
															this.config.ShowDeletedMessages,
															this.config.AllowQuickReply,
															this.config.AuthorNameSetting
														);
				model.Page = (page.HasValue && page.Value > 0 ? page.Value : 1);

				model.Path = new Dictionary<String, String>();
				// Build the breadcrumb path.
				HomeController.BuildPath(topic, model.Path, this.Url);

				return View(model);
			}

			topic = this.context.GetRepository<Topic>().Read(id);
			// The user is not allowed to read the forum, let's tell him!
			TempData.Add("Reason", ForumHelper.GetString<mvcForum.Web.ForumConfigurator>("NoAccess.Forum", new { Name = topic.Forum.Name }));
			// Off to the "no access" page.
			return RedirectToRoute("NoAccess");
		}
		#endregion

		[Authorize]
		public ActionResult Moderate(Int32 id) {
			Topic topic = this.GetRepository<Topic>().Read(id);
			if (topic.Forum.HasAccess(AccessFlag.Moderator)) {
				Dictionary<string, string> path = new Dictionary<string, string>();
				// Create the breadcrumb path!
				HomeController.BuildPath(topic, path, this.Url);

				return View(new UpdateTopicViewModel { Id = topic.Id, Title = topic.Title, Body = topic.Posts.OrderBy(p => p.Position).First().Body, Flag = topic.Flag, Type = topic.Type, Path = path, IsModerator = topic.Forum.HasAccess(AccessFlag.Moderator), Reason = topic.Posts.OrderBy(p => p.Position).First().EditReason });
			}

			// The user is not allowed to edit the topic, let's tell him!
			TempData.Add("Reason", ForumHelper.GetString("NoAccess.ModeratorForum", new { Name = topic.Forum.Name }));
			// Off to the "no access" page!
			return RedirectToRoute("NoAccess");
		}

		[Authorize]
		[HttpPost]
		[NotBanned]
		// TODO: Remove this and get it working with AllowHtml instead!!
		[ValidateInput(false)]
		public ActionResult Moderate(UpdateTopicViewModel model) {
			if (ModelState.IsValid) {
				Topic topic = this.GetRepository<Topic>().Read(model.Id);
				if (this.topicService.Update(this.ActiveUser, topic, model.Title, model.Body, model.Type, model.Flag, model.Reason, Url.RouteUrl("ShowForum", new { id = topic.Forum.Id, title = topic.Forum.Name.ToSlug(), area = "forum" }))) {
					return RedirectToAction("index", "moderate", new RouteValueDictionary { { "id", topic.Forum.Id }, { "area", "forum" } });
				}
			}

			return View(model);
		}

		#region Update
		[Authorize]
		public ActionResult Edit(Int32 id) {
			Topic topic = this.GetRepository<Topic>().Read(id);
			if ((topic.Forum.HasAccess(AccessFlag.Edit) && this.ActiveUser.Id == topic.Author.Id) || topic.Forum.HasAccess(AccessFlag.Moderator)) {
				Dictionary<string, string> path = new Dictionary<string, string>();
				// Create the breadcrumb path!
				HomeController.BuildPath(topic, path, this.Url);

				return View(new UpdateTopicViewModel { Id = topic.Id, Title = topic.Title, Body = topic.Posts.OrderBy(p => p.Position).First().Body, Flag = topic.Flag, Type = topic.Type, Path = path, IsModerator = topic.Forum.HasAccess(AccessFlag.Moderator) });
			}

			// The user is not allowed to edit the topic, let's tell him!
			TempData.Add("Reason", ForumHelper.GetString("NoAccess.EditTopic"));
			// Off to the "no access" page!
			return RedirectToRoute("NoAccess");
		}

		[Authorize]
		[HttpPost]
		[NotBanned]
		// TODO: Remove this and get it working with AllowHtml instead!!
		[ValidateInput(false)]
		public ActionResult Edit(UpdateTopicViewModel model) {
			if (ModelState.IsValid) {
				Topic topic = this.GetRepository<Topic>().Read(model.Id);
				if (this.topicService.Update(this.ActiveUser, topic, model.Title, model.Body, Url.RouteUrl("ShowForum", new { id = topic.Forum.Id, title = topic.Forum.Name.ToSlug(), area = "forum" }))) {
					return RedirectToRoute("ShowTopic", new RouteValueDictionary { { "id", topic.Id }, { "title", topic.Title.ToSlug() } });
				}
			}

			return View(model);
		}
		#endregion

		#region Follow/unfollow
		[Authorize]
		public ActionResult Follow(Int32 topicId) {
			Topic topic = this.GetRepository<Topic>().Read(topicId);

			// Any authenticated user?
			if (this.Authenticated) {
				FollowTopic ft = new FollowTopic(topic, this.ActiveUser);
				this.GetRepository<FollowTopic>().Create(ft);
				this.Context.SaveChanges();
			}

			return RedirectToRoute("ShowTopic", new RouteValueDictionary { { "id", topic.Id }, { "title", topic.Title.ToSlug() } });
		}

		[Authorize]
		public ActionResult UnFollow(Int32 topicId) {
			Topic topic = this.GetRepository<Topic>().Read(topicId);

			// Any authenticated user?
			if (this.Authenticated) {
				IRepository<FollowTopic> ftRepository = this.GetRepository<FollowTopic>();
				// Get the follow topic.
				FollowTopic ft = ftRepository.ReadOne(new FollowTopicSpecifications.SpecificTopicAndUser(topic, this.ActiveUser));
				// Did we get one?
				if (ft != null) {
					// Then the user is following the topic, let's delete the "link".
					ftRepository.Delete(ft);

					this.Context.SaveChanges();
				}
			}

			return RedirectToRoute("ShowTopic", new RouteValueDictionary { { "id", topic.Id }, { "title", topic.Title.ToSlug() } });
		}
		#endregion
	}
}